/***********************************************************************************
 * 文件名： mcu_adc.c
 * 作者： 黄均铭
 * 版本： 1
 * 说明：
 * 		ADC驱动。
 * 修改记录：
 * 	2024年1月8日: 初版。 黄均铭。
 ***********************************************************************************/
#include "mcu.h"

static volatile bool mIsBusy = false;            // 标记正在转换中
static adc_result_callback_t mResultCall = NULL; // 转换结果回调

/****************************************************************************
 ** \brief	ADCB_IRQHandler
 **
 ** \param [in]  none
 ** \return none
 ** \note
 ****************************************************************************/
void ADCB_IRQHandler(void)
{
    adc_t result;
    if (ADCB->MIS & (0x1UL << ADCB_CH_0))
    {
        result = ADCB->DATA[0] & 0xFFF;
        mIsBusy = false;
        // 清除转换标记
        ADCB->LOCK = ADCB_LOCK_WRITE_KEY;
        ADCB->ICLR |= (0x01UL << ADCB_CH_0);
        ADCB->LOCK = 0x00;
        // 通知转换结果
        if (mResultCall != NULL)
        {
            mResultCall(result);
        }
    }
}

// ADC初始化
void Adc_Init()
{
    /* 设置ADCB时钟 */
    SYS_EnablePeripheralClk(SYS_CLK_ADCB_MSK);

    // ADC寄存器解锁
    ADCB->LOCK = ADCB_LOCK_WRITE_KEY;

    // 低电流模式 NSMP采样时间 软件通道 VDD正端 VSS负端 单次转换
    ADCB->CON = ADCB_CON_ADMODE10_Msk | (ADC_NSMP << ADCB_CON_ADCNSMP_Pos) | ADCB_CON_ADCSWCHE_Msk | (ADC_VS << ADCB_CON_ADCVS_Pos) | (ADC_DIV << ADCB_CON_ADCDIV_Pos);

    // 内部30通道选择 默认通道配置
    ADCB->CON2 = (ADC_ICHS << ADCB_CON2_ADCICHS_Pos) | ADC_DEFAULT;

    /* 设置ADCB通道使能，结果通道 */
    ADCB->SCAN |= ADCB_CH_0_MSK;

    /* 设置ADCB中断 */
    ADCB->IMSC |= ADCB_CH_0_MSK;
    NVIC_EnableIRQ(ADCB_IRQn);
    NVIC_SetPriority(ADCB_IRQn, 2); /*优先级0~3， 0最高、3最低*/

#if ADC_ENABLE != 0
    /* 开启ADCB */
    ADCB->CON |= ADCB_CON_ADCEN_Msk;
#endif

    // ADC寄存器锁定
    ADCB->LOCK = 0x00;
}

// ADC采集一次
adc_t Adc_GetOnce(u8 ch)
{
    adc_t result;
    u16 timeout;

    // 等待忙完
    timeout = 0;
    while (mIsBusy)
    {
        /* code */
        timeout++;
        if (timeout > 10000)
            return 0;
    }

    // ADC寄存器解锁
    ADCB->LOCK = ADCB_LOCK_WRITE_KEY;

    // 暂时关闭ADC中断
    ADCB->IMSC &= ~ADCB_CH_0_MSK;

    // 设置通道
    ADCB->CON2 &= ~ADCB_CON2_ADCSWCHS_Msk;
    ADCB->CON2 |= (ch & ADCB_CON2_ADCSWCHS_Msk);

    // 启动ADC
    ADCB->CON2 |= ADCB_CON2_ADCST_Msk;

    // ADC寄存器锁定
    ADCB->LOCK = 0x00;

    // 等待转换完成
    timeout = 0;
    while (ADCB->CON2 & ADCB_CON2_ADCST_Msk)
    {
        /* code */
        timeout++;
        if (timeout > 10000)
            break;
    }

    // ADC寄存器解锁
    ADCB->LOCK = ADCB_LOCK_WRITE_KEY;

    // 清除中断标记
    ADCB->ICLR |= (0x01UL << ADCB_CH_0);

    // 重新开启ADC中断
    ADCB->IMSC |= ADCB_CH_0_MSK;

    // ADC寄存器锁定
    ADCB->LOCK = 0x00;

    // 获取转换结果
    result = ADCB->DATA[0] & 0xFFF;

    return result;
}

u16 MedianFilter16b9(u16* dat);
// [同步]获取一次有效的（中值滤波）AD值
// 注意：需要先把对应通道的IO口设置为模拟信号模式
adc_t Adc_GetValue(u8 ch)
{
    u8 i;
    adc_t adv[9];
    adc_t result;

    for (i = 0; i < 9; i++) // 连续采集9次
    {
        adv[i] = Adc_GetOnce(ch);
    }
    result = MedianFilter16b9(adv); // 中值滤波
    return result;
}

// [同步]获取VDD电压，单位mV
u16 Adc_GetVDDVoltage()
{
    adc_t adv = Adc_GetValue(ADCH_VREF);
    u16 bv = ((u32)ADC_VOL_VREF * ADC_FULL_VALUE) / adv;
    return bv;
}

// [异步]开始一次转换，完成后会通过调用 result_callback 通知转换完成和传递结果
// 将会等待ADC不繁忙时（完成了其它异步转换任务）返回
void Adc_StartOnce(u8 ch, adc_result_callback_t result_callback)
{
    u16 timeout;

    // 等待忙完
    timeout = 0;
    while (mIsBusy)
    {
        /* code */
        timeout++;
        if (timeout > 10000)
            return;
    }

    // 设置回调
    mResultCall = result_callback;

    // ADC寄存器解锁
    ADCB->LOCK = ADCB_LOCK_WRITE_KEY;

    // 设置通道
    ADCB->CON2 &= ~ADCB_CON2_ADCSWCHS_Msk;
    ADCB->CON2 |= (ch & ADCB_CON2_ADCSWCHS_Msk);

    // 启动ADC
    ADCB->CON2 |= ADCB_CON2_ADCST_Msk;

    // ADC寄存器锁定
    ADCB->LOCK = 0x00;
}
